Skip to contentMethod: mergeDetachedInternal(Object, Descriptor)
1: /**
2: * Copyright (C) 2016 Czech Technical University in Prague
3: * <p>
4: * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
5: * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
6: * version.
7: * <p>
8: * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9: * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10: * details. You should have received a copy of the GNU General Public License along with this program. If not, see
11: * <http://www.gnu.org/licenses/>.
12: */
13: package cz.cvut.kbss.jopa.sessions;
14:
15: import cz.cvut.kbss.jopa.adapters.IndirectCollection;
16: import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException;
17: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
18: import cz.cvut.kbss.jopa.model.AbstractEntityManager;
19: import cz.cvut.kbss.jopa.model.EntityManagerImpl.State;
20: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
21: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
22: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
23: import cz.cvut.kbss.jopa.model.metamodel.Metamodel;
24: import cz.cvut.kbss.jopa.model.query.Query;
25: import cz.cvut.kbss.jopa.model.query.TypedQuery;
26: import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory;
27: import cz.cvut.kbss.jopa.sessions.change.ChangeManagerImpl;
28: import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl;
29: import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory;
30: import cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator;
31: import cz.cvut.kbss.jopa.utils.*;
32:
33: import java.lang.reflect.Field;
34: import java.net.URI;
35: import java.util.*;
36: import java.util.Map.Entry;
37:
38: public class UnitOfWorkImpl extends AbstractSession implements UnitOfWork, QueryFactory, ConfigurationHolder, Wrapper {
39:
40: private final Map<Object, Object> cloneMapping;
41: private final Map<Object, Object> cloneToOriginals;
42: private final Map<Object, Object> keysToClones;
43: private Map<Object, Object> deletedObjects;
44: private Map<Object, Object> newObjectsCloneToOriginal;
45: private Map<Object, Object> newObjectsOriginalToClone;
46: private Map<Object, Object> newObjectsKeyToClone;
47: private RepositoryMap repoMap;
48:
49: private boolean hasChanges;
50: private boolean hasNew;
51: private boolean hasDeleted;
52: private boolean shouldReleaseAfterCommit;
53: private boolean shouldClearCacheAfterCommit;
54: private boolean useTransactionalOntology;
55:
56: private boolean isActive;
57: private boolean inCommit;
58:
59: private UnitOfWorkChangeSet uowChangeSet;
60:
61: private AbstractSession parent;
62: private AbstractEntityManager entityManager;
63: private final ConnectionWrapper storage;
64:
65: private final MergeManager mergeManager;
66: private final CloneBuilder cloneBuilder;
67: private final ChangeManager changeManager;
68: private final QueryFactory queryFactory;
69: private final CollectionFactory collectionFactory;
70: /**
71: * This is a shortcut for the second level cache.
72: */
73: private final CacheManager cacheManager;
74:
75: public UnitOfWorkImpl(AbstractSession parent) {
76: this.parent = Objects.requireNonNull(parent, ErrorUtils.constructNPXMessage("parent"));
77: this.cloneMapping = createMap();
78: this.cloneToOriginals = createMap();
79: this.keysToClones = new HashMap<>();
80: this.repoMap = new RepositoryMap();
81: repoMap.initDescriptors();
82: this.cloneBuilder = new CloneBuilderImpl(this);
83: this.collectionFactory = new CollectionFactory(this);
84: this.cacheManager = parent.getLiveObjectCache();
85: this.storage = acquireConnection();
86: this.queryFactory = new SparqlQueryFactory(this, storage);
87: this.mergeManager = new MergeManagerImpl(this);
88: this.changeManager = new ChangeManagerImpl(this);
89: this.inCommit = false;
90: this.useTransactionalOntology = true;
91: this.isActive = true;
92: }
93:
94: /**
95: * This method returns null, since we don't support nested Units of Work yet.
96: */
97: @Override
98: public UnitOfWork acquireUnitOfWork() {
99: return null;
100: }
101:
102: @Override
103: protected ConnectionWrapper acquireConnection() {
104: final ConnectionWrapper conn = parent.acquireConnection();
105: conn.setUnitOfWork(this);
106: return conn;
107: }
108:
109: @Override
110: public <T> T readObject(Class<T> cls, Object primaryKey, Descriptor descriptor) {
111: Objects.requireNonNull(cls, ErrorUtils.constructNPXMessage("cls"));
112: Objects.requireNonNull(primaryKey, ErrorUtils.constructNPXMessage("primaryKey"));
113: Objects.requireNonNull(descriptor, ErrorUtils.constructNPXMessage("descriptor"));
114:
115: return readObjectInternal(cls, primaryKey, descriptor);
116: }
117:
118: private <T> T readObjectInternal(Class<T> cls, Object primaryKey, Descriptor descriptor) {
119: assert cls != null;
120: assert primaryKey != null;
121: assert descriptor != null;
122: // First try to find the object among new uncommitted objects
123: Object result = getNewObjectsKeyToClone().get(primaryKey);
124: if (result != null && (isInRepository(descriptor, result))) {
125: // The result can be returned, since it is already registered in
126: // this UOW
127: return cls.cast(result);
128: }
129: // Object is already managed
130: result = keysToClones.get(primaryKey);
131: if (result != null && isInRepository(descriptor, result) && !getDeletedObjects().containsKey(result)) {
132: return cls.cast(result);
133: }
134: // Search the cache
135: result = getObjectFromCache(cls, primaryKey, descriptor.getContext());
136: if (result == null) {
137: // The object is not in the session cache, so search the ontology
138: final URI pkUri = EntityPropertiesUtils.getValueAsURI(primaryKey);
139: result = storage.find(new LoadingParameters<>(cls, pkUri, descriptor));
140: }
141: if (result == null) {
142: return null;
143: }
144: Object clone = registerExistingObject(result, descriptor);
145: checkForCollections(clone);
146: return cls.cast(clone);
147: }
148:
149: /**
150: * This method calculates the changes that were to the registered entities and adds these changes into the given
151: * change set for future commit to the ontology.
152: */
153: private void calculateChanges() {
154: final UnitOfWorkChangeSet changeSet = getUowChangeSet();
155: if (hasNew()) {
156: calculateNewObjects(changeSet);
157: }
158: if (hasDeleted()) {
159: calculateDeletedObjects(changeSet);
160: }
161: }
162:
163: /**
164: * Create object change sets for the new objects and adds them into our UnitOfWorkChangeSet.
165: *
166: * @param changeSet UnitOfWorkChangeSet
167: */
168: private void calculateNewObjects(UnitOfWorkChangeSet changeSet) {
169: for (Object clone : getNewObjectsCloneToOriginal().keySet()) {
170: final Descriptor c = getDescriptor(clone);
171: Object original = getNewObjectsCloneToOriginal().get(clone);
172: if (original == null) {
173: original = this.cloneBuilder.buildClone(clone, c);
174: }
175: if (original == null) {
176: throw new OWLPersistenceException(
177: "Error while calculating changes for new objects. Original not found.");
178: }
179: getNewObjectsCloneToOriginal().put(clone, original);
180: getNewObjectsOriginalToClone().put(original, clone);
181: changeSet.addNewObjectChangeSet(ChangeSetFactory.createObjectChangeSet(original, clone,
182: c));
183: }
184: }
185:
186: private void calculateDeletedObjects(final UnitOfWorkChangeSet changeSet) {
187: for (Object clone : getDeletedObjects().keySet()) {
188: Object original = cloneToOriginals.get(clone);
189: if (original == null) {
190: throw new OWLPersistenceException("Cannot find an original for clone!");
191: }
192: Descriptor descriptor = getDescriptor(clone);
193: changeSet.addDeletedObjectChangeSet(ChangeSetFactory.createObjectChangeSet(original, clone,
194: descriptor));
195: }
196: }
197:
198: public void clear() {
199: cloneMapping.clear();
200: cloneToOriginals.clear();
201: keysToClones.clear();
202: this.deletedObjects = null;
203: this.newObjectsCloneToOriginal = null;
204: this.newObjectsOriginalToClone = null;
205: this.newObjectsKeyToClone = null;
206: this.hasChanges = false;
207: this.hasDeleted = false;
208: this.hasNew = false;
209: }
210:
211: public boolean contains(Object entity) {
212: Objects.requireNonNull(entity, ErrorUtils.constructNPXMessage("entity"));
213:
214: return isObjectManaged(entity);
215: }
216:
217: public void commit() {
218: LOG.trace("UnitOfWork commit started.");
219: if (!isActive()) {
220: throw new IllegalStateException("Cannot commit inactive Unit of Work!");
221: }
222: this.inCommit = true;
223: commitUnitOfWork();
224: LOG.trace("UnitOfWork commit finished.");
225: }
226:
227: public void rollback() {
228: LOG.trace("UnitOfWork rollback started.");
229: if (!isActive()) {
230: throw new IllegalStateException("Cannot rollback inactive Unit of Work!");
231: }
232: storage.rollback();
233: clear();
234: }
235:
236: /**
237: * Commit this Unit of Work.
238: */
239: protected void commitUnitOfWork() {
240: commitToOntology();
241: mergeChangesIntoParent();
242: postCommit();
243: }
244:
245: /**
246: * Clean up after the commit.
247: */
248: private void postCommit() {
249: // Remove indirect collections from clones
250: cloneMapping.keySet().forEach(this::removeIndirectCollections);
251: getNewObjectsCloneToOriginal().clear();
252: getNewObjectsOriginalToClone().clear();
253: getNewObjectsKeyToClone().clear();
254: getDeletedObjects().clear();
255: cloneToOriginals.clear();
256: cloneMapping.clear();
257: keysToClones.clear();
258: this.hasChanges = false;
259: this.hasDeleted = false;
260: this.hasNew = false;
261: this.inCommit = false;
262: cloneBuilder.reset();
263: this.repoMap = new RepositoryMap();
264: repoMap.initDescriptors();
265: this.uowChangeSet = null;
266: if (shouldClearCacheAfterCommit) {
267: cacheManager.evictAll();
268: this.shouldReleaseAfterCommit = true;
269: }
270: }
271:
272: /**
273: * If there are any changes, commit them to the ontology.
274: */
275: protected void commitToOntology() {
276: boolean hasChanges = this.hasNew || this.hasChanges || this.hasDeleted;
277: if (hasChanges) {
278: calculateChanges();
279: }
280: validateIntegrityConstraints();
281: storageCommit();
282: }
283:
284: private void validateIntegrityConstraints() {
285: if (uowChangeSet == null) {
286: return;
287: }
288: final IntegrityConstraintsValidator validator = IntegrityConstraintsValidator.getValidator();
289: for (ObjectChangeSet changeSet : uowChangeSet.getNewObjects()) {
290: validator.validate(changeSet.getCloneObject(),
291: getMetamodel().entity((Class<Object>) changeSet.getObjectClass()), false);
292: }
293: uowChangeSet.getExistingObjectsChanges().forEach(changeSet -> validator.validate(changeSet, getMetamodel()));
294: }
295:
296: private Map<Object, Object> createMap() {
297: return new IdentityHashMap<>();
298: }
299:
300: /**
301: * Gets current state of the specified entity. </p>
302: * <p>
303: * Note that since no repository is specified we can only determine if the entity is managed or removed. Therefore
304: * if the case is different this method returns State#NOT_MANAGED.
305: *
306: * @param entity The entity to check
307: * @return State of the entity
308: */
309: public State getState(Object entity) {
310: Objects.requireNonNull(entity, ErrorUtils.constructNPXMessage("entity"));
311:
312: if (getDeletedObjects().containsKey(entity)) {
313: return State.REMOVED;
314: } else if (getNewObjectsCloneToOriginal().containsKey(entity)) {
315: return State.MANAGED_NEW;
316: } else if (cloneMapping.containsKey(entity)) {
317: return State.MANAGED;
318: } else {
319: return State.NOT_MANAGED;
320: }
321: }
322:
323: /**
324: * Checks the state of the specified entity with regards to the specified repository.
325: *
326: * @param entity Object
327: * @param descriptor Entity descriptor
328: * @return The state of the specified entity
329: */
330: public State getState(Object entity, Descriptor descriptor) {
331: Objects.requireNonNull(entity, ErrorUtils.constructNPXMessage("entity"));
332: Objects.requireNonNull(descriptor, ErrorUtils.constructNPXMessage("descriptor"));
333:
334: if (getDeletedObjects().containsKey(entity)) {
335: return State.REMOVED;
336: } else if (cloneMapping.containsKey(entity) && isInRepository(descriptor, entity)) {
337: if (getNewObjectsCloneToOriginal().containsKey(entity)) {
338: return State.MANAGED_NEW;
339: }
340: return State.MANAGED;
341: } else {
342: return State.NOT_MANAGED;
343: }
344: }
345:
346: /**
347: * Tries to find the original object for the given clone. It searches the existing objects, new objects and deleted
348: * objects.
349: *
350: * @param clone Object
351: * @return The original object for the given clone
352: */
353: public Object getOriginal(Object clone) {
354: if (clone == null) {
355: return null;
356: }
357: Object original = cloneToOriginals.get(clone);
358: if (original == null) {
359: original = getNewObjectsCloneToOriginal().get(clone);
360: }
361: return original;
362: }
363:
364: /**
365: * Gets managed original with the specified identifier or {@code null} if there is none matching.
366: * <p>
367: * Descriptor is used to check repository context validity.
368: *
369: * @param cls Return type of the original
370: * @param identifier Instance identifier
371: * @param descriptor Repository descriptor
372: * @return Original object managed by this UoW or {@code null} if this UoW doesn't contain a matching instance
373: */
374: public <T> T getManagedOriginal(Class<T> cls, Object identifier, Descriptor descriptor) {
375: if (!keysToClones.containsKey(identifier)) {
376: return null;
377: }
378: final Object clone = keysToClones.get(identifier);
379: if (!cls.isAssignableFrom(clone.getClass())) {
380: return null;
381: }
382: if (!isInRepository(descriptor, clone)) {
383: return null;
384: }
385: return cls.cast(cloneToOriginals.get(clone));
386: }
387:
388: /**
389: * Check if this UnitOfWork contains this original entity. This method is used by the CloneBuilder so it does not
390: * have to clone already managed referenced objects.
391: *
392: * @param entity The original entity.
393: * @return True if the original is managed in this UnitOfWork.
394: */
395: boolean containsOriginal(Object entity) {
396: return entity != null && cloneToOriginals.containsValue(entity);
397: }
398:
399: /**
400: * Finds clone for the specified original. This method assumes that the original is managed in this persistence
401: * context (UnitOfWork). However, if not, this method just goes through all the managed objects and if it does not
402: * find match, returns null.
403: *
404: * @param original The original object whose clone we are looking for.
405: * @return The clone or null, if there is none.
406: */
407: Object getCloneForOriginal(Object original) {
408: for (Entry<Object, Object> entry : cloneToOriginals.entrySet()) {
409: // We use IdentityMap, so we can use ==
410: if (entry.getValue() == original) {
411: return entry.getKey();
412: }
413: }
414: return null;
415: }
416:
417: public boolean hasDeleted() {
418: return hasDeleted;
419: }
420:
421: public boolean hasChanges() {
422: return hasChanges || hasDeleted || hasNew;
423: }
424:
425: public boolean hasNew() {
426: return hasNew;
427: }
428:
429: public void setHasChanges() {
430: this.hasChanges = true;
431: }
432:
433: public Map<Object, Object> getDeletedObjects() {
434: if (deletedObjects == null) {
435: this.deletedObjects = createMap();
436: }
437: return deletedObjects;
438: }
439:
440: public Map<Object, Object> getNewObjectsCloneToOriginal() {
441: if (newObjectsCloneToOriginal == null) {
442: this.newObjectsCloneToOriginal = createMap();
443: }
444: return newObjectsCloneToOriginal;
445: }
446:
447: public Map<Object, Object> getNewObjectsOriginalToClone() {
448: if (newObjectsOriginalToClone == null) {
449: this.newObjectsOriginalToClone = createMap();
450: }
451: return newObjectsOriginalToClone;
452: }
453:
454: public Map<Object, Object> getNewObjectsKeyToClone() {
455: if (newObjectsKeyToClone == null) {
456: // Cannot use identity map, since it compares the key references
457: // which may not be the same
458: this.newObjectsKeyToClone = new HashMap<>();
459: }
460: return newObjectsKeyToClone;
461: }
462:
463: @Override
464: public CacheManager getLiveObjectCache() {
465: return parent.getLiveObjectCache();
466: }
467:
468: public UnitOfWorkChangeSet getUowChangeSet() {
469: if (uowChangeSet == null) {
470: this.uowChangeSet = ChangeSetFactory.createUoWChangeSet();
471: }
472: return uowChangeSet;
473: }
474:
475: public boolean isActive() {
476: return this.isActive;
477: }
478:
479: /**
480: * Returns true if the given clone represents a newly created object. Otherwise returns false.
481: *
482: * @param clone Object
483: * @return boolean
484: */
485: public boolean isObjectNew(Object clone) {
486: return clone != null && getNewObjectsCloneToOriginal().containsKey(clone);
487: }
488:
489: /**
490: * Returns true if the given object is already managed.
491: *
492: * @param entity Object
493: * @return boolean
494: */
495: public boolean isObjectManaged(Object entity) {
496: Objects.requireNonNull(entity, ErrorUtils.constructNPXMessage("entity"));
497:
498: return (cloneMapping.containsKey(entity) && !getDeletedObjects().containsKey(entity));
499: }
500:
501: private boolean doesEntityExist(Object entity, Object primaryKey, Descriptor descriptor) {
502: assert entity != null;
503: assert descriptor != null;
504: if (cloneMapping.containsKey(entity) && !getDeletedObjects().containsKey(entity)
505: && isInRepository(descriptor, entity)) {
506: return true;
507: }
508: return primaryKey != null
509: && cacheManager.contains(entity.getClass(), primaryKey, descriptor.getContext());
510: }
511:
512: /**
513: * Persists changed value of the specified field.
514: *
515: * @param entity Entity with changes (the clone)
516: * @param f The field whose value has changed
517: * @throws IllegalStateException If this UoW is not in transaction
518: */
519: public void attributeChanged(Object entity, Field f) {
520: if (!isInTransaction()) {
521: throw new IllegalStateException("This unit of work is not in a transaction.");
522: }
523: final Descriptor descriptor = getDescriptor(entity);
524: if (descriptor == null) {
525: throw new OWLPersistenceException("Unable to find repository for entity " + entity
526: + ". Is it registered in this UoW?");
527: }
528: storage.merge(entity, f, descriptor);
529: createChangeRecord(entity, f, descriptor);
530: setHasChanges();
531: setIndirectCollectionIfPresent(entity, f);
532: }
533:
534: private void createChangeRecord(Object clone, Field field, Descriptor descriptor) {
535: final Object orig = getOriginal(clone);
536: if (orig == null) {
537: return;
538: }
539: final ChangeRecord record = new ChangeRecordImpl(field.getName(),
540: EntityPropertiesUtils.getFieldValue(field, clone));
541: registerChangeRecord(clone, orig, descriptor, record);
542: }
543:
544: private void registerChangeRecord(Object clone, Object orig, Descriptor descriptor, ChangeRecord record) {
545: ObjectChangeSet chSet = getUowChangeSet().getExistingObjectChanges(orig);
546: if (chSet == null) {
547: chSet = ChangeSetFactory.createObjectChangeSet(orig, clone, descriptor);
548: getUowChangeSet().addObjectChangeSet(chSet);
549: }
550: chSet.addChangeRecord(record);
551: }
552:
553: /**
554: * Merge the changes from this Unit of Work's change set into the server session.
555: */
556: public void mergeChangesIntoParent() {
557: if (hasChanges()) {
558: mergeManager.mergeChangesFromChangeSet(getUowChangeSet());
559: }
560: }
561:
562: @Override
563: public <T> T mergeDetached(T entity, Descriptor descriptor) {
564: Objects.requireNonNull(entity, ErrorUtils.constructNPXMessage("entity"));
565: Objects.requireNonNull(descriptor, ErrorUtils.constructNPXMessage("descriptor"));
566:
567: final Object pk = getIdentifier(entity);
568: if (!storage.contains(pk, entity.getClass(), descriptor)) {
569: registerNewObject(entity, descriptor);
570: return entity;
571: } else {
572: return mergeDetachedInternal(entity, descriptor);
573: }
574: }
575:
576: private <T> T mergeDetachedInternal(T entity, Descriptor descriptor) {
577:• assert entity != null;
578: final Object iri = getIdentifier(entity);
579: final Class<T> entityCls = (Class<T>) entity.getClass();
580: // Search the cache
581: T original = getObjectFromCache(entityCls, iri, descriptor.getContext());
582:• if (original == null) {
583: // The object is not in the session cache, so search the ontology
584: final URI idUri = EntityPropertiesUtils.getValueAsURI(iri);
585: original = storage.find(new LoadingParameters<>(entityCls, idUri, descriptor, true));
586: }
587:• assert original != null;
588: registerClone(entity, original, descriptor);
589: try {
590: // Merge only the changed attributes
591: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(original, entity, descriptor);
592: changeManager.calculateChanges(chSet);
593: final EntityType<?> et = getMetamodel().entity(entityCls);
594:• for (ChangeRecord record : chSet.getChanges().values()) {
595: final Field field = et.getFieldSpecification(record.getAttributeName()).getJavaField();
596: storage.merge(entity, field, descriptor);
597: }
598: getUowChangeSet().addObjectChangeSet(chSet);
599: } catch (OWLEntityExistsException e) {
600: unregisterObject(entity);
601: throw e;
602: } catch (IllegalAccessException e) {
603: throw new OWLPersistenceException(e);
604: }
605:• if (cacheManager.contains(entityCls, iri, descriptor.getContext())) {
606: cacheManager.evict(entityCls, iri, descriptor.getContext());
607: }
608: setHasChanges();
609: return entity;
610: }
611:
612: /**
613: * {@inheritDoc}
614: */
615: @Override
616: void registerEntityWithPersistenceContext(Object entity, UnitOfWorkImpl uow) {
617: parent.registerEntityWithPersistenceContext(entity, uow);
618: }
619:
620: @Override
621: void deregisterEntityFromPersistenceContext(Object entity, UnitOfWork uow) {
622: parent.deregisterEntityFromPersistenceContext(entity, uow);
623: }
624:
625: /**
626: * {@inheritDoc}
627: */
628: public Object registerExistingObject(Object entity, Descriptor descriptor) {
629: if (entity == null) {
630: return null;
631: }
632: if (cloneToOriginals.containsValue(entity)) {
633: return getCloneForOriginal(entity);
634: }
635: Object clone = this.cloneBuilder.buildClone(entity, descriptor);
636: assert clone != null;
637: registerClone(clone, entity, descriptor);
638: return clone;
639: }
640:
641: private void registerClone(Object clone, Object original, Descriptor descriptor) {
642: cloneMapping.put(clone, clone);
643: cloneToOriginals.put(clone, original);
644: final Object identifier = EntityPropertiesUtils.getPrimaryKey(clone, getMetamodel());
645: keysToClones.put(identifier, clone);
646: registerEntityWithPersistenceContext(clone, this);
647: registerEntityWithOntologyContext(descriptor, clone);
648: }
649:
650: /**
651: * Release this Unit of Work. Releasing an active Unit of Work with uncommitted changes causes all pending changes
652: * to be discarded.
653: */
654: public void release() {
655: clear();
656: storage.close();
657: this.isActive = false;
658: LOG.debug("UnitOfWork released.");
659: }
660:
661: @Override
662: public <T> void revertObject(T object) {
663: Objects.requireNonNull(object, ErrorUtils.constructNPXMessage("object"));
664:
665: if (!isObjectManaged(object) && !getDeletedObjects().containsKey(object)) {
666: throw new IllegalArgumentException("The specified enity " + object
667: + " is not managed by this persistence context.");
668: }
669: final Descriptor descriptor = getDescriptor(object);
670: if (descriptor == null) {
671: throw new IllegalArgumentException("Unable to find entity " + object
672: + " in this persistence context.");
673: }
674: // To revert the object's state, just swap original and clone for change
675: // calculation and merging so that the state of the original is merged
676: // into the state of the clone
677: final Object original = getOriginal(object);
678: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(object, original,
679: descriptor);
680: try {
681: final boolean anyChanges = changeManager.calculateChanges(chSet);
682: if (anyChanges) {
683: mergeManager.mergeChangesOnObject(original, chSet);
684: }
685: } catch (IllegalAccessException | IllegalArgumentException e) {
686: throw new OWLPersistenceException(e);
687: }
688: }
689:
690: @Override
691: public void registerNewObject(Object entity, Descriptor descriptor) {
692: Objects.requireNonNull(entity, ErrorUtils.constructNPXMessage("entity"));
693: Objects.requireNonNull(descriptor, ErrorUtils.constructNPXMessage("descriptor"));
694:
695: registerNewObjectInternal(entity, descriptor);
696: }
697:
698: /**
699: * Registers the specified entity for persist in this Unit of Work.
700: *
701: * @param entity The entity to register
702: * @param descriptor Entity descriptor, specifying optionally contexts into which the entity will be persisted
703: */
704: private void registerNewObjectInternal(Object entity, Descriptor descriptor) {
705: assert entity != null;
706: Object id = getIdentifier(entity);
707: if (id == null) {
708: final EntityType<?> eType = getMetamodel().entity(entity.getClass());
709: EntityPropertiesUtils.verifyIdentifierIsGenerated(entity, eType);
710: }
711: if (doesEntityExist(entity, id, descriptor) && !entity.getClass().isEnum()) {
712: throw new OWLEntityExistsException("An entity with URI " + id
713: + " is already persisted in repository " + descriptor);
714: }
715: storage.persist(id, entity, descriptor);
716: if (id == null) {
717: // If the ID was null, extract it from the entity
718: // It is present now
719: id = getIdentifier(entity);
720: }
721: // Original is null until commit
722: cloneMapping.put(entity, entity);
723: getNewObjectsCloneToOriginal().put(entity, null);
724: registerEntityWithPersistenceContext(entity, this);
725: registerEntityWithOntologyContext(descriptor, entity);
726: getNewObjectsKeyToClone().put(id, entity);
727: checkForCollections(entity);
728: this.hasNew = true;
729: }
730:
731: /**
732: * Remove the specified entity from the ontology.
733: *
734: * @param entity Managed entity to delete
735: */
736: public void removeObject(Object entity) {
737: if (entity == null) {
738: return;
739: }
740: if (!isObjectManaged(entity)) {
741: throw new IllegalArgumentException(
742: "Cannot remove entity which is not managed in the current persistence context.");
743: }
744: if (getDeletedObjects().containsKey(entity)) {
745: return;
746: }
747: final Object primaryKey = getIdentifier(entity);
748: final Descriptor descriptor = getDescriptor(entity);
749:
750: if (hasNew() && getNewObjectsCloneToOriginal().containsKey(entity)) {
751: unregisterObject(entity);
752: getNewObjectsKeyToClone().remove(primaryKey);
753: } else {
754: getDeletedObjects().put(entity, entity);
755: this.hasDeleted = true;
756: }
757: //                unregisterEntityFromOntologyContext(entity);
758: storage.remove(primaryKey, entity.getClass(), descriptor);
759: }
760:
761: /**
762: * Remove the registered object from this Unit of Work.
763: *
764: * @param object Clone of the original object
765: */
766: public void unregisterObject(Object object) {
767: if (object == null) {
768: return;
769: }
770: cloneMapping.remove(object);
771: cloneToOriginals.remove(object);
772:
773: getDeletedObjects().remove(object);
774: if (hasNew()) {
775: Object newOriginal = getNewObjectsCloneToOriginal().remove(object);
776: if (newOriginal != null) {
777: getNewObjectsOriginalToClone().remove(newOriginal);
778: }
779: }
780: removeIndirectCollections(object);
781: deregisterEntityFromPersistenceContext(object, this);
782: unregisterEntityFromOntologyContext(object);
783: }
784:
785: public boolean shouldReleaseAfterCommit() {
786: return shouldReleaseAfterCommit;
787: }
788:
789: public void setShouldClearAfterCommit(boolean shouldClearCache) {
790: this.shouldClearCacheAfterCommit = shouldClearCache;
791: }
792:
793: public void setEntityManager(AbstractEntityManager entityManager) {
794: this.entityManager = entityManager;
795: }
796:
797: public void writeUncommittedChanges() {
798: if (!hasChanges()) {
799: return;
800: }
801: commitUnitOfWork();
802: }
803:
804: @Override
805: public Metamodel getMetamodel() {
806: return parent.getMetamodel();
807: }
808:
809: @Override
810: public boolean isTypeManaged(Class<?> cls) {
811: return parent.isTypeManaged(cls);
812: }
813:
814: @Override
815: public boolean isInTransaction() {
816: return entityManager != null && entityManager.getTransaction().isActive();
817: }
818:
819: /**
820: * Returns {@code true} if this UoW is currently committing changes.
821: */
822: public boolean isInCommit() {
823: return inCommit;
824: }
825:
826: @Override
827: public <T> void loadEntityField(T entity, Field field) {
828: Objects.requireNonNull(entity, ErrorUtils.constructNPXMessage("entity"));
829: Objects.requireNonNull(field, ErrorUtils.constructNPXMessage("field"));
830:
831: if (EntityPropertiesUtils.getFieldValue(field, entity) != null) {
832: return;
833: }
834: final Descriptor entityDescriptor = getDescriptor(entity);
835: if (entityDescriptor == null) {
836: throw new OWLPersistenceException(
837: "Unable to find repository identifier for entity " + entity
838: + ". Is it managed by this UoW?");
839: }
840: storage.loadFieldValue(entity, field, entityDescriptor);
841: final Object orig = EntityPropertiesUtils.getFieldValue(field, entity);
842: final Object entityOriginal = getOriginal(entity);
843: if (entityOriginal != null) {
844: EntityPropertiesUtils.setFieldValue(field, entityOriginal, orig);
845: }
846: final Descriptor fieldDescriptor = getFieldDescriptor(entity, field, entityDescriptor);
847: final Object clone = cloneLoadedFieldValue(entity, field, fieldDescriptor, orig);
848: EntityPropertiesUtils.setFieldValue(field, entity, clone);
849: }
850:
851: private <T> Descriptor getFieldDescriptor(T entity, Field field, Descriptor entityDescriptor) {
852: final EntityType<?> et = getMetamodel().entity(entity.getClass());
853: final FieldSpecification<?, ?> fieldSpec = et
854: .getFieldSpecification(field.getName());
855: return entityDescriptor.getAttributeDescriptor(fieldSpec);
856: }
857:
858: private <T> Object cloneLoadedFieldValue(T entity, Field field, final Descriptor fieldDescriptor,
859: final Object fieldValueOrig) {
860: Object clone;
861: if (fieldValueOrig == null) {
862: clone = null;
863: } else {
864: if (isTypeManaged(field.getType())) {
865: clone = registerExistingObject(fieldValueOrig, fieldDescriptor);
866: final URI fieldContext = fieldDescriptor.getContext();
867: putObjectIntoCache(getIdentifier(clone), fieldValueOrig, fieldContext);
868: } else {
869: clone = cloneBuilder.buildClone(entity, field, fieldValueOrig, fieldDescriptor);
870: }
871: }
872: return clone;
873: }
874:
875: @Override
876: public void removeObjectFromCache(Object toRemove, URI context) {
877: Objects.requireNonNull(toRemove, ErrorUtils.constructNPXMessage("toRemove"));
878:
879: final Object primaryKey = getIdentifier(toRemove);
880: cacheManager.evict(toRemove.getClass(), primaryKey, context);
881: }
882:
883: @Override
884: public boolean isConsistent(URI context) {
885: return storage.isConsistent(context);
886: }
887:
888: @Override
889: public List<URI> getContexts() {
890: return storage.getContexts();
891: }
892:
893: @Override
894: public void setUseTransactionalOntologyForQueryProcessing() {
895: this.useTransactionalOntology = true;
896: }
897:
898: @Override
899: public boolean useTransactionalOntologyForQueryProcessing() {
900: return useTransactionalOntology;
901: }
902:
903: @Override
904: public void setUseBackupOntologyForQueryProcessing() {
905: this.useTransactionalOntology = false;
906: }
907:
908: @Override
909: public boolean useBackupOntologyForQueryProcessing() {
910: return !useTransactionalOntology;
911: }
912:
913: @Override
914: public Query createNativeQuery(String sparql) {
915: return queryFactory.createNativeQuery(sparql);
916: }
917:
918: @Override
919: public <T> TypedQuery<T> createNativeQuery(String sparql, Class<T> resultClass) {
920: return queryFactory.createNativeQuery(sparql, resultClass);
921: }
922:
923: @Override
924: public Query createQuery(String query) {
925: return queryFactory.createQuery(query);
926: }
927:
928: @Override
929: public <T> TypedQuery<T> createQuery(String query, Class<T> resultClass) {
930: return queryFactory.createQuery(query, resultClass);
931: }
932:
933: /**
934: * Check if the specified entity contains a collection. If so, replace it with its indirect representation so that
935: * changes in that collection can be tracked.
936: *
937: * @param entity The entity to check
938: */
939: private void checkForCollections(Object entity) {
940: Field[] fields = entity.getClass().getDeclaredFields();
941: for (Field f : fields) {
942: setIndirectCollectionIfPresent(entity, f);
943: }
944: }
945:
946: /**
947: * Create and set indirect collection on the specified entity field.</p>
948: * <p>
949: * If the specified field is of Collection type and it is not already an indirect collection, create new one and set
950: * it as the value of the specified field on the specified entity.
951: *
952: * @param entity The entity collection will be set on
953: * @param field The field to set
954: * @throws IllegalArgumentException Reflection
955: */
956: public void setIndirectCollectionIfPresent(Object entity, Field field) {
957: Objects.requireNonNull(entity, ErrorUtils.constructNPXMessage("entity"));
958: Objects.requireNonNull(field, ErrorUtils.constructNPXMessage("field"));
959:
960: Object value = EntityPropertiesUtils.getFieldValue(field, entity);
961: if (value == null || value instanceof IndirectCollection) {
962: return;
963: }
964: if (value instanceof Collection || value instanceof Map) {
965: EntityPropertiesUtils.setFieldValue(field, entity, createIndirectCollection(value, entity, field));
966: }
967: }
968:
969: /**
970: * Creates an indirect collection, which wraps the specified collection instance and propagates changes to the
971: * persistence context.
972: *
973: * @param collection Collection to be proxied
974: * @param owner Collection owner instance
975: * @param field Field filled with the collection
976: * @return Indirect collection
977: */
978: public IndirectCollection<?> createIndirectCollection(Object collection, Object owner, Field field) {
979: return collectionFactory.createIndirectCollection(collection, owner, field);
980: }
981:
982: /**
983: * Remove indirect collection implementations from the specified entity (if present).
984: *
985: * @param entity The entity to remove indirect collections from
986: */
987: private void removeIndirectCollections(Object entity) {
988: Field[] fields = entity.getClass().getDeclaredFields();
989: for (Field f : fields) {
990: final Object ob = EntityPropertiesUtils.getFieldValue(f, entity);
991: if (ob == null) {
992: continue;
993: }
994: if (ob instanceof IndirectCollection) {
995: IndirectCollection<?> indCol = (IndirectCollection<?>) ob;
996: EntityPropertiesUtils.setFieldValue(f, entity, indCol.getReferencedCollection());
997: }
998: }
999: }
1000:
1001: /**
1002: * Get entity with the specified primary key from the cache. </p>
1003: * <p>
1004: * If the cache does not contain any object with the specified primary key and class, null is returned. This method
1005: * is just a delegate for the cache methods, it handles locks.
1006: *
1007: * @return Cached object or null
1008: */
1009: private <T> T getObjectFromCache(Class<T> cls, Object primaryKey, URI context) {
1010: assert cls != null;
1011: assert primaryKey != null;
1012: return cacheManager.get(cls, primaryKey, context);
1013: }
1014:
1015: public void putObjectIntoCache(Object primaryKey, Object entity, URI context) {
1016: cacheManager.add(primaryKey, entity, context);
1017: }
1018:
1019: private Object getIdentifier(Object entity) {
1020: assert entity != null;
1021: return EntityPropertiesUtils.getPrimaryKey(entity, getMetamodel());
1022: }
1023:
1024: private void unregisterEntityFromOntologyContext(Object entity) {
1025: assert entity != null;
1026:
1027: final Descriptor descriptor = repoMap.getEntityDescriptor(entity);
1028: if (descriptor == null) {
1029: throw new OWLPersistenceException("Fatal error, unable to find descriptor for entity " + entity);
1030: }
1031:
1032: repoMap.remove(descriptor, entity);
1033: repoMap.removeEntityToRepository(entity);
1034: }
1035:
1036: private void registerEntityWithOntologyContext(Descriptor repository, Object entity) {
1037: assert repository != null;
1038: assert entity != null;
1039:
1040: repoMap.add(repository, entity, null);
1041: repoMap.addEntityToRepository(entity, repository);
1042: }
1043:
1044: private boolean isInRepository(Descriptor descriptor, Object entity) {
1045: assert descriptor != null;
1046: assert entity != null;
1047:
1048: return repoMap.contains(descriptor, entity);
1049: }
1050:
1051: private Descriptor getDescriptor(Object entity) {
1052: assert entity != null;
1053:
1054: return repoMap.getEntityDescriptor(entity);
1055: }
1056:
1057: private void storageCommit() {
1058: try {
1059: storage.commit();
1060: } catch (OWLPersistenceException e) {
1061: entityManager.removeCurrentPersistenceContext();
1062: throw e;
1063: }
1064: }
1065:
1066: @Override
1067: public Configuration getConfiguration() {
1068: return entityManager.getConfiguration();
1069: }
1070:
1071: @Override
1072: public <T> T unwrap(Class<T> cls) {
1073: if (cls.isAssignableFrom(getClass())) {
1074: return cls.cast(this);
1075: }
1076: return storage.unwrap(cls);
1077: }
1078: }